package xiaye.crawler.core.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.ByteArrayBuffer;
import org.apache.http.util.EntityUtils;

import xiaye.crawler.core.CrawlerThread;
import xiaye.crawler.core.Frontier;
import xiaye.crawler.core.LinkParser;
import xiaye.crawler.domain.Page;
import xiaye.crawler.exception.ResponseParseException;
import xiaye.crawler.exception.ResponseTooLargeException;
import xiaye.crawler.core.PageStore;

import com.google.inject.Inject;
import com.ibm.icu.text.CharsetDetector;
import com.ibm.icu.text.CharsetMatch;

public class SimpleCrawlerThread extends CrawlerThread{
	Frontier frontier;
	PageStore pageStore;
	HttpFetcher httpFetcher;
	URI uri;
	Page page;
	LinkParser linkParser;
	
	@Inject
	public SimpleCrawlerThread(Frontier frontier, PageStore pageStore, HttpFetcher httpFetcher, Page page, LinkParser linkParser){
		this.frontier = frontier;
		this.pageStore = pageStore;
		this.httpFetcher = httpFetcher;
		this.page = page;
		this.linkParser = linkParser;
	}
	
	@Override
	public void setUri(URI uri){
		this.uri = uri;
	}
	
	@Override
	public void run(){
		try {
			HttpContext context = new BasicHttpContext();
			HttpGet get = new HttpGet(uri);
			HttpResponse response = httpFetcher.fetch(get, context);
			//printStatistic(response, context);
			processToPage(response, context);
			pageStore.storePage(page);
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ResponseParseException e) {
			e.printStackTrace();
		}
	}

	private void printStatistic(HttpResponse response, HttpContext context) {
		System.out.println(context.getAttribute(ExecutionContext.HTTP_TARGET_HOST));
		Header[] headers = response.getAllHeaders();
		for (Header header: headers){
			System.out.println(header);
		}
	}
	
	private void processToPage(HttpResponse response, HttpContext context) throws ResponseParseException {
		try {
			setStartUri();
			setFinalUri(context);
			byte[] responseBody = getResponseBody(response);
			String encoding = getEncoding(responseBody);
			String content = parseResponseBody(responseBody, encoding);
			setEncoding(encoding);
			setContent(content);
			setOutlinks(page.getFinalUri(), content);
		} catch (URISyntaxException e){
			throw new ResponseParseException();
		} catch (ResponseTooLargeException e) {
			throw new ResponseParseException();
		} catch (IOException e) {
			throw new ResponseParseException();
		}
	}
	private void setStartUri() {
		page.setStartUri(uri);
	}

	private void setFinalUri(HttpContext context) throws URISyntaxException {
		String host = context.getAttribute(ExecutionContext.HTTP_TARGET_HOST).toString();
		String path = ((HttpRequest)context.getAttribute(ExecutionContext.HTTP_REQUEST)).getRequestLine().getUri().toString();
		URI finalUri = new URI(host + path);
		page.setFinalUri(finalUri);
	}

	private byte[] getResponseBody(HttpResponse response) throws ResponseTooLargeException, IOException {
		int maxSize = 1024 * 1024;
		HttpEntity entity = response.getEntity();
        if (entity.getContentLength() > maxSize) {
            throw new ResponseTooLargeException();
        }
        int initialLength = (int)entity.getContentLength();
        if (initialLength < 0) {
        	initialLength = 4096;
        }
        ByteArrayBuffer buffer = new ByteArrayBuffer(initialLength);
        InputStream instream = entity.getContent();
        if (instream == null) {
            return new byte[] {};
        }
        try {
            byte[] tmp = new byte[4096];
            int len;
            while((len = instream.read(tmp)) != -1) {
                buffer.append(tmp, 0, len);
                if (buffer.length() > maxSize){
                	throw new ResponseTooLargeException();
                }
            }
        } finally {
            instream.close();
        }
        return buffer.toByteArray();
	}

	private String getEncoding(byte[] responseBody) {
		CharsetDetector detector = new CharsetDetector();
		detector.setText(responseBody);
		CharsetMatch match = detector.detect();
		String encoding = match.getName();
		return encoding;
	}

	private String parseResponseBody(byte[] responseBody, String encoding) throws UnsupportedEncodingException {
		return new String(responseBody, encoding);
	}

	private void setEncoding(String encoding) {
		page.setEncoding(encoding);
	}

	private void setContent(String content) {
		page.setContent(content);
	}

	private void setOutlinks(URI self, String content) {
		linkParser.parse(self, content);	
		for (URI uri: linkParser.getOutlinks()){
			System.out.println(uri);
		}
		//page.setOutlinks(linkParser.getOutlinks());
		page.setOutlinks(null);
	}
}
